package com.jdpc.weparty.security

import com.jdpc.weparty.component.ServerConfig
import io.jsonwebtoken.Claims
import io.jsonwebtoken.Jwts
import io.jsonwebtoken.SignatureAlgorithm
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.beans.factory.annotation.Value
import org.springframework.security.core.userdetails.UserDetails
import org.springframework.stereotype.Component
import java.util.*

@Component
class JwtTokenUtil {
//    @Value("\${jwt.secret}")
//    private lateinit var secret: String
//
//    @Value("\${jwt.expiration}")
//    private var expiration: Long? = null
//
//    @Value("\${jwt.tokenHead}")
//    private lateinit var tokenHead: String

    @Autowired
    private lateinit var serverConfig: ServerConfig
    fun getAccountFromToken(token: String): String? {
        val claims = getClaimsFromToken(token)
        return claims?.subject
    }

    fun getCreatedDateFromToken(token: String): Date? {

        val claims: Claims? = getClaimsFromToken(token)

        return claims?.get(CLAIM_KEY_CREATED)?.let {
            Date(it as Long)
        }

    }

    fun getSessionFromToken(token: String): String? {
        val claims: Claims? = getClaimsFromToken(token)

        return claims?.get(CLAIM_KEY_SESSIONKEY)?.let {
            it as String
        }
    }

    fun getOpenIdFromToken(token: String): String? {
        val claims: Claims? = getClaimsFromToken(token)

        return claims?.get(CLAIM_KEY_TOKEN_TYPE)?.let {
            it as String
        }
    }

    fun getTokenTypeFromToken(token: String): TokenType? {
        val claims: Claims? = getClaimsFromToken(token)

        return claims?.get(CLAIM_KEY_TOKEN_TYPE)?.let {
            TokenType.valueOf(it as String)
        }

    }

    fun getExpirationDateFromToken(token: String): Date? {

        val claims: Claims? = getClaimsFromToken(token)

        return claims?.expiration
    }

    private fun getClaimsFromToken(token: String): Claims? {

        return try {
            Jwts.parser()
                    .setSigningKey(serverConfig.secret)
                    .parseClaimsJws(token)
                    .body
        } catch (e: Exception) {
            e.printStackTrace()
            return null
        }
    }

    private fun generateExpirationDate(): Date {
        return Date(System.currentTimeMillis() + serverConfig.expiration!! * 1000)
    }

    private fun isTokenExpired(token: String): Boolean {
        val expiration = getExpirationDateFromToken(token)
        return expiration!!.before(Date())
    }

    private fun isCreatedBeforeLastPasswordReset(created: Date?, lastPasswordReset: Date?): Boolean {
        return lastPasswordReset != null && created!!.before(lastPasswordReset)
    }

    /**
     * 统一使用UserDetails生成token
     */
    fun generateToken(userDetails: UserDetails, sessionKey: String): String {
        val claims: MutableMap<String, Any> = HashMap()
        claims[CLAIM_KEY_USERNAME] = userDetails.username
        if (userDetails is JwtUser) {
            claims[CLAIM_KEY_OPENID] = userDetails.openId
        }
        claims[CLAIM_KEY_SESSIONKEY] = sessionKey
        claims[CLAIM_KEY_TOKEN_TYPE] = TokenType.WX
        claims[CLAIM_KEY_CREATED] = Date()
        return generateToken(claims)
    }

    fun generateToken(userDetails: UserDetails): String {
        val claims: MutableMap<String, Any> = HashMap()
        claims[CLAIM_KEY_USERNAME] = userDetails.username
        claims[CLAIM_KEY_TOKEN_TYPE] = TokenType.PHONE
        claims[CLAIM_KEY_CREATED] = Date()
        return generateToken(claims)
    }

    private fun generateToken(claims: Map<String, Any>): String {
        return Jwts.builder()
                .setClaims(claims)
                .setExpiration(generateExpirationDate())
                .signWith(SignatureAlgorithm.HS512, serverConfig.secret)
                .compact()
    }

    fun canTokenBeRefreshed(token: String, lastPasswordReset: Date?): Boolean {
        val created = getCreatedDateFromToken(token)
        return (!isCreatedBeforeLastPasswordReset(created, lastPasswordReset)
                && !isTokenExpired(token))
    }

    fun refreshToken(token: String): String? {
        var refreshedToken: String? = null
        val claims: Claims? = getClaimsFromToken(token)
        claims?.let {
            it[CLAIM_KEY_CREATED] = Date()
            refreshedToken = generateToken(claims)
        }
        return refreshedToken
    }

    fun validateToken(token: String, userDetails: UserDetails): Boolean {
        val user = userDetails as JwtUser
        val type = getTokenTypeFromToken(token)
        val created = getCreatedDateFromToken(token)

        return when (type) {
            TokenType.PHONE -> {
                val username = getAccountFromToken(token)
                //final Date expiration = getExpirationDateFromToken(token);

                (username == user.username && !isTokenExpired(token)
                        && !isCreatedBeforeLastPasswordReset(created, user.lastPasswordResetDate))

            }
            TokenType.WX -> {
                val openId = getOpenIdFromToken(token)
                val username = getAccountFromToken(token)
                //final Date expiration = getExpirationDateFromToken(token);
                (username == user.username &&
                        openId == user.openId &&
                        !isTokenExpired(token) &&
                        !isCreatedBeforeLastPasswordReset(created, user.lastPasswordResetDate))

            }
            else -> {
                false
            }
        }
    }

    companion object {
        //        const val TOKEN_TYPE_WX = "wx"
//        const val TOKEN_TYPE_PHONE = "phone"
        private const val CLAIM_KEY_SESSIONKEY = "sessionKey"
        private const val CLAIM_KEY_OPENID = "openId"
        private const val CLAIM_KEY_TOKEN_TYPE = "type"
        private const val CLAIM_KEY_USERNAME = "sub"
        private const val CLAIM_KEY_CREATED = "created"
    }

    enum class TokenType {
        WX, PHONE
    }
}